#include<stdio.h>
#include<stdlib.h>
#include <mpi.h>
#include <omp.h>
#include <string.h>

#define BLOCK_LOW(id,p,n) (((id)*(n))/(p))
#define BLOCK_HIGH(id,p,n) (BLOCK_LOW((id)+1,(p),(n)) - 1)

float	**Alocar_matriz_real (int, int);
float	**Liberar_matriz_real (int, int, float **);
void	LeituraArquivo(int,int,int,FILE *,int,float **);
float * Alocar_vetor_real(int);
int * Alocar_vetor_int(int);
float * Liberar_vetor_real(int, float *);
int * Liberar_vetor_int(int, int *);

int main(int argc, char *argv[])
{

	int	ordemMatriz;			// J_ORDER
	int	filaAvaliacao;			// J_ROW_TEST
	float	erro;				// J_ERROR
	int	interacoes;			// J_ITE_MAX
	int	nl;				// Indica o numero de  linha que cada processo deve ler do arquivo
	float	**matriz;			//matriz que contem os dados
	int	i,j,k;				//manipulacao loops
	float	aux;
	int	daux;
	FILE	*fp;
	float	*x;				//resultado local das iterações
	float	*xg;				//resultado global das iterações
	short 	flag = 0;
	int 	*rcv_disp;				//indices do MPI_Allgatherv
	int 	*rcv_cnt;	

	/*Variáveis MPI */   
        int 		dest,noProcesses, processId, src, tag;
        MPI_Status 	status;   

        /* Inicializacção do MPI */   
        MPI_Init(&argc, &argv);   
        MPI_Comm_size(MPI_COMM_WORLD, &noProcesses);
        MPI_Comm_rank(MPI_COMM_WORLD, &processId);

	//abre o arquivo para leitura - todos os nos possuem esse arquivo
	if ( (fp = fopen(argv[1],"r")) == NULL ) 
        {
                printf("Erro ao abrir o arquivo.\n");
                exit(1);
        }

	//leitura dos dados iniciais - Todos os nos precisao desse valor
	fscanf(fp,"%d",&ordemMatriz);
	fscanf(fp,"%d",&filaAvaliacao);
	fscanf(fp,"%f",&erro);
	fscanf(fp,"%d",&interacoes);
	
	//divide o numero de linha que cada processo deve ler
	nl=(int)(ordemMatriz/noProcesses);
	if(ordemMatriz%noProcesses>0) {
			flag = 1;
			if(processId==0) {
				flag = 0;
				nl++;
			}
				
	}

		
	//reserva memoria necessaria para cada nó 
	matriz=Alocar_matriz_real (nl + 1,ordemMatriz); // +1 porque tb contera os dados da matriz
	x = Alocar_vetor_real(nl);
	xg = Alocar_vetor_real(ordemMatriz);
	rcv_disp = Alocar_vetor_int(noProcesses);
	rcv_cnt = Alocar_vetor_int(noProcesses);
	
	//define os vetores utilizados em MPI_Allgetherv
	if (ordemMatriz%noProcesses>0) {
		rcv_disp[0] = 0;
		if (processId == 0) rcv_cnt[0] = nl;
		else rcv_cnt[0] = nl+1;
		for ( i = 1; i < noProcesses; i++ ) {

			rcv_disp[i] = (int)BLOCK_LOW(i,noProcesses,ordemMatriz)+1;
			if (processId == 0) rcv_cnt[i] = nl-1;
			else rcv_cnt[i] = nl;

		}
	}
	else {

		for ( i = 0; i < noProcesses; i++ ) {

			rcv_disp[i] = (int)BLOCK_LOW(i,noProcesses,ordemMatriz);
			rcv_cnt[i] = nl;

		}

	}

/*	if ( processId == 1 ) {
	for ( i = 0; i < noProcesses; i++ ) {

		printf("%d ",rcv_cnt[i]);

	}
	printf("\n");
	for ( i = 0; i < noProcesses; i++ ) {

		printf("%d ",rcv_disp[i]);

	}
	}*/

	//Leitura arquivo
	LeituraArquivo(processId,noProcesses,nl,fp,ordemMatriz,matriz);

	//divide tudo pela diagonal principal
	k=(int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+flag;
	for(i=0;i<nl;i++)
	{
		aux=matriz[i][k];
		for(j=0;j<ordemMatriz;j++)
			matriz[i][j]=matriz[i][j]/aux;

		matriz[nl][k]/=aux;
		//printf("%f\n", matriz[nl][(int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+i+flag]);
		//printf("%d\n", (int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+i+flag);
		k++;
	}
	//Aqui dividimos o vetor, mas para isso temos que recuperar os dados das demaisz
	/*printf("%d\n",processId);
	for(i=0;i<nl+1;i++) {
		for(j=0;j<ordemMatriz;j++)
			printf("%.2f\t",matriz[i][j]);
		printf("\n");
	}*/

	printf("\n\n");

	printf("%d\n",processId);
	for(i=0;i<nl;i++) {
		x[i] = matriz[nl][(int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+i+flag];
		//printf("%f\n", matriz[nl][(int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+i+flag]);
		//printf("%d\n", (int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+i+flag);
	}


	printf("%d\n",processId);
	for(j=0;j<nl;j++)
		printf("%.2f ",x[j]);

	printf("\n\n");

	k = 1;

	MPI_Allgatherv(x,nl,MPI_FLOAT,xg,rcv_cnt,rcv_disp, MPI_FLOAT,MPI_COMM_WORLD);
		//atualizar_vetor(float*:xg,float*:x,int:(int)BLOCK_LOW(processId,noProcesses,ordemMatriz)+i+flag, int:nl, float**:matriz);


	if ( processId == 0 ) {

		for ( i = 0; i < ordemMatriz; i++ )
			printf("%.2f ", xg[i]);

	}

	matriz=Liberar_matriz_real (nl+1,ordemMatriz,matriz);
	x=Liberar_vetor_real(ordemMatriz,x);

	MPI_Finalize( );
	
	return 0;
}

void atualizar_vetor(float *xg, float *x, int pos, int nl, float **matriz) {

	int i = 0;

	for ( i = 0; i < nl;  i++ ) {		

		x[i] = matriz[nl][pos];

	}

}

void LeituraArquivo(int processId,int noProcesses,int nl,FILE *fp,int ordem,float **mat)
{
	int tamanho,flag=0;
	int i,j;
	long int aux;
	
	if(processId == 0)
		for(i=0;i<nl;i++)
			for(j=0;j<ordem;j++)
			{
				fscanf(fp,"%f",&mat[i][j]);
			}
	else
	{
		if(ordem%noProcesses>0)flag++;
		tamanho=2*ordem-1; //no minimo, cada linha tera um digito segido de um espaco, o ultimo digito nao tera espaco (-1)
		//posiciona na posicao correta		
		for(i=0;i<((processId*nl)+flag);i++)
		{
			fseek(fp, ftell(fp)+tamanho, SEEK_SET);
			while (getc(fp)!= '\n');
		}
		//agora basta ler o arquivo
		for(i=0;i<nl;i++)
			for(j=0;j<ordem;j++)
			{
				fscanf(fp,"%f",&mat[i][j]);
			}
	}
	//vai no final do arquivo e le de traz pra frente o vetor final
	fseek(fp, 0, SEEK_END);
	fseek(fp, ftell(fp)-2, SEEK_SET);	
	
	for(j=ordem-1;j>=0;j--)
	{
		while (getc(fp)!= '\n')
			fseek(fp,ftell(fp)-2, SEEK_SET);

		aux=ftell(fp);
		fscanf(fp,"%f",&mat[nl][j]);
		fseek(fp, aux-2, SEEK_SET);	
	}
	
}

float **Alocar_matriz_real (int m, int n)
{
	float	**v;  // ponteiro para a matriz 
	int   	i;    // variavel auxiliar    

 	if (m < 1 || n < 1) // verifica parametros recebidos
	{ 
	     printf ("** Erro: Parametro invalido **\n");
	     return (NULL);
     	}

  	// aloca as linhas da matriz 
  	v = (float **) calloc (m, sizeof(float *));
  	if (v == NULL) 
	{
	     printf ("** Erro: Memoria Insuficiente **");
	     return (NULL);
     	}
	
  	// aloca as colunas da matriz
  	for ( i = 0; i < m; i++ ) 
	{
      		v[i] = (float*) calloc (n, sizeof(float));
     		if (v[i] == NULL) 
		{
         		printf ("** Erro: Memoria Insuficiente **");
         		return (NULL);
         	}
      	}
  	return (v); // retorna o ponteiro para a matriz
}

float **Liberar_matriz_real (int m, int n, float **v)
{
	int	i;  // variavel auxiliar

  	if (v == NULL) return (NULL);
  	if (m < 1 || n < 1) // verifica parametros recebidos
	{  
    		printf ("** Erro: Parametro invalido **\n");
     		return (v);
     	}
  	for (i=0; i<m; i++) free (v[i]);// libera as linhas da matriz 
  	free (v);      			// libera a matriz 
  	return (NULL);			// retorna um ponteiro nulo 
}

float *Alocar_vetor_real (int n)
{
	float	*v; // ponteiro para o vetor

	if (n < 1) // verifica parametros recebidos
	{  
	     printf ("** Erro: Parametro invalido **\n");
	     return (NULL);
     	}

  	// aloca o vetor
  	v = (float *) calloc (n+1, sizeof(float));
  	if (v == NULL) 
	{
	     printf ("** Erro: Memoria Insuficiente **");
	     return (NULL);
     	}
  	return (v);    /* retorna o ponteiro para o vetor */
}

int *Alocar_vetor_int (int n)
{
	int	*v; // ponteiro para o vetor

	if (n < 1) // verifica parametros recebidos
	{  
	     printf ("** Erro: Parametro invalido **\n");
	     return (NULL);
     	}

  	// aloca o vetor
  	v = (int *) calloc (n+1, sizeof(int));
  	if (v == NULL) 
	{
	     printf ("** Erro: Memoria Insuficiente **");
	     return (NULL);
     	}
  	return (v);    /* retorna o ponteiro para o vetor */
}

float *Liberar_vetor_real (int n, float *v)
{
	if (v == NULL) return (NULL);
  	if (n < 1) // verifica parametros recebidos
	{ 
     		printf ("** Erro: Parametro invalido **\n");
     		return (NULL);
     	}
  	free(v);        // libera o vetor
  	return (NULL);  // retorna o ponteiro
}
















